﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Rung.EBook.WebSiteAnalizer.Exceptions;
using Rung.EBook.WebSiteAnalizer.Driver;
using Rung.EBook.Common.Model;
using System.Data;
using Rung.EBook.Common.Driver;
using System.Runtime.Serialization;

namespace Rung.EBook.WebSiteAnalizer
{
    /// <summary>
    /// 定义网站解析类。
    /// 此类包含了网站解析的正则表达式。
    /// </summary>
    [Serializable]
    public class AnalizerBase : ISerializable
    {
        private Encoding mEncoding = Encoding.Unicode;
        private HttpMethod method = HttpMethod.Get;
        private string[] mBookRegexTag = new string[] { "url", "title" };
        private string[] mChapterRegexTag = new string[] { "url", "title" };

        /// <summary>
        /// 网站名称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 【附带信息】网站名称
        /// </summary>
        public string SiteName { get; set; }

        /// <summary>
        /// 【附带信息】网站首页网址
        /// </summary>
        public string SiteUrl;

        /// <summary>
        /// 网站 HTML 的编码格式。
        /// </summary>
        public Encoding Encoding { get { return this.mEncoding; } set { this.mEncoding = value; } }

        /// <summary>
        /// 网站参数类型（POST 或者 GET）。
        /// </summary>
        public HttpMethod Method { get { return this.method; } set { this.method = value; } }

        /// <summary>
        /// 书籍名称前缀。
        /// 例如：将《毛泽东文选》为书籍列表，则书籍名称可能是“第一卷”、“第二卷”等，这时就要给他们加上前缀（“毛泽东文选”）。
        /// </summary>
        public string BookNamePrefix { get; set; }

        /// <summary>
        /// 书籍名称。
        /// 例如：将《毛泽东文选》为书籍列表，则书籍名称可能是“第一卷”、“第二卷”等。
        /// </summary>
        public string BookName { get; set; }

        /// <summary>
        /// 【书籍列表页面】URL。
        /// http://blog.guqu.net/user1/14/archives/2006/1936.html
        /// </summary>
        public string BooksUrl { get; set; }

        /// <summary>
        /// 【书籍列表页面】的【目录】匹配正则。
        /// 该正则包含两个组：[url]每个书籍URL中变化的部分；[title]书名。
        /// </summary>
        public string BookRegex { get; set; }

        /// <summary>
        /// 正则表达式中的组名。
        /// 在此处，一个是【url：书籍的URL】一个是【title：书名】
        /// </summary>
        public string[] BookRegexTag { get { return this.mBookRegexTag; } set { this.mBookRegexTag = value; } }

        /// <summary>
        /// 【章节列表页面】URL。
        /// http://blog.guqu.net/user1/14/archives/2006/1936.html
        /// </summary>
        public string ChaptersUrl { get; set; }

        /// <summary>
        /// 【章节列表页面】的【目录】匹配正则。
        /// 该正则包含两个组：[url]每个章节URL中变化的部分；[title]章节标题。
        /// </summary>
        public string ChapterRegex { get; set; }

        /// <summary>
        /// 正则表达式中的组名。
        /// 在此处，一个是【url：正文的URL】一个是【title：标题】
        /// </summary>
        public string[] ChapterRegexTag { get { return this.mChapterRegexTag; } set { this.mChapterRegexTag = value; } }

        /// <summary>
        /// 【正文页面】URL。
        /// http://www.my285.com/gdwx/xs/xy/syqz/{0}.htm
        /// </summary>
        public string ContentBaseUrl { get; set; }

        /// <summary>
        /// 【正文页面】的【正文内容】匹配正则。
        /// </summary>
        public string ContentRegex { get; set; }

        /// <summary>
        /// 【正文页面】的【正文内容垃圾文字】匹配正则。
        /// key：匹配垃圾文字的正则；value：替换为的文字。
        /// </summary>
        public Dictionary<string, string> ContentClears { get; set; }

        /// <summary>
        /// 根据解析器中的【章节列表URL】，获取【章节列表】，并构造【书籍实体】的【章节实体集合】。
        /// 此时，书籍实体中的章节集合，已经具备了章节URL和章节标题信息，没有章节正文。
        /// </summary>
        /// <param name="bBM">书籍实体</param>
        public virtual BookBase FillBook(BookBase bBM)
        {
            if (string.IsNullOrEmpty(this.ChaptersUrl)) throw (new AnalizerInfoNullException("网页解析器中的章节列表 URL 为空。"));
            if (string.IsNullOrEmpty(this.ChapterRegex)) throw (new AnalizerInfoNullException("网页解析器中的章节正则表达式为空。"));

            string html = HttpHelper.Get(this.ChaptersUrl, Encoding);
            //string html = HttpHelper.GetByTcp(this.ChapterUrl);
            DataTable RegMatchData = RegexHelper.GetCaptures(this.ChapterRegex, html, this.ChapterRegexTag);

            bBM.Chapters.Clear();

            if (RegMatchData != null && RegMatchData.Rows.Count > 0)
            {
                int index = 0;
                DataView dv = RegMatchData.AsDataView();
                dv.Sort = "url";
                DataTable table = dv.ToTable();

                foreach (DataRow row in table.Rows)
                {
                    ChapterBase c = new ChapterBase
                    {
                        Index = index++,
                        Url = string.Format(this.ContentBaseUrl, row[this.ChapterRegexTag[0]]),
                        Title = row[this.ChapterRegexTag[1]].ToString()
                    };

                    bBM.Chapters.Add(c);
                }
            }
            return bBM;
        }

        /// <summary>
        /// 根据 章节实体中的Url 获取正文，并填充到 章节实体 中。
        /// 这里的正文内容为网页中原版，没有做任何修改。
        /// </summary>
        /// <returns>正文内容</returns>
        public virtual string FillChapter(ChapterBase cBM)
        {
            if (string.IsNullOrEmpty(cBM.Url)) throw (new AnalizerInfoNullException("网页解析器中的正文 URL 为空。"));

            string html = HttpHelper.Get(cBM.Url, this.Encoding, this.method);

            string result = html;

            // 过滤正文中的垃圾文字
            if (this.ContentClears != null && this.ContentClears.Count > 0)
            {
                foreach (var r in this.ContentClears)
                {
                    result = RegexHelper.Replace(r.Key, result, r.Value);
                }
            }

            List<string> contents = RegexHelper.GetMatch(this.ContentRegex, result);

            if (contents.Count == 0)
            {
                return null;
            }
            //else if (contents.Count > 1)
            //{
            //    throw (new Exception("解析到的匹配项不唯一，解析正则表达式需要进一步优化。"));
            //}
            else
            {
                result = contents[0];
                return cBM.Content = StaticFunctions.HtmlToTxt(result);
            }
        }

        /// <summary>
        /// 填充书籍列表。
        /// </summary>
        /// <param name="dictionary">key：书籍URL；value：书名。</param>
        public void FillBooksList(Dictionary<string, string> dictionary)
        {
            if (string.IsNullOrEmpty(this.BooksUrl)) throw (new AnalizerInfoNullException("网页解析器中的书籍列表 URL 为空。"));
            if (string.IsNullOrEmpty(this.BookRegex)) throw (new AnalizerInfoNullException("网页解析器中的书籍正则表达式为空。"));

            string html = HttpHelper.Get(this.BooksUrl, Encoding);
            //string html = HttpHelper.GetByTcp(this.ChapterUrl);
            DataTable RegMatchData = RegexHelper.GetCaptures(this.BookRegex, html, this.BookRegexTag);

            dictionary.Clear();

            if (RegMatchData != null && RegMatchData.Rows.Count > 0)
            {
                int index = 0;
                foreach (DataRow row in RegMatchData.Rows)
                {
                    dictionary.Add(
                        row[this.ChapterRegexTag[0]].ToString(), 
                        row[this.ChapterRegexTag[1]].ToString());
                }
            }
        }

        public static AnalizerBase GetAnalizerForUse(AnalizerBase analizer, string bookId, string bookName)
        {
            AnalizerBase analizerForUse = new AnalizerBase();

            analizerForUse.Name = analizer.Name;
            analizerForUse.SiteName = analizer.SiteName;
            analizerForUse.SiteUrl = analizer.SiteUrl;
            analizerForUse.Method = analizer.Method;
            analizerForUse.Encoding = analizer.Encoding;

            analizerForUse.BookName = bookName;
            analizerForUse.BookNamePrefix = analizer.BookNamePrefix;
            analizerForUse.BookRegex = analizer.BookRegex;
            analizerForUse.BookRegexTag = analizer.BookRegexTag;
            analizerForUse.BooksUrl = analizer.BooksUrl;

            analizerForUse.ChapterRegex = analizer.ChapterRegex.Replace("{0}", bookId);
            analizerForUse.ChapterRegexTag = analizer.ChapterRegexTag;
            analizerForUse.ChaptersUrl = string.Format(analizer.ChaptersUrl, bookId);

            analizerForUse.ContentRegex = analizer.ContentRegex;
            if (!string.IsNullOrEmpty(analizerForUse.BookRegex))
                analizerForUse.ContentBaseUrl = analizer.ContentBaseUrl.Replace("{0}", bookId).Replace("{1}", "{0}");
            else// 若没有书籍获取逻辑，则为直接获取章节的情况；不需要填充书籍Id。
                analizerForUse.ContentBaseUrl = analizer.ContentBaseUrl;
            analizerForUse.ContentClears = analizer.ContentClears;

            return analizerForUse;
        }

        public AnalizerBase Clone()
        {
            AnalizerBase analizer = new AnalizerBase
            {
                Name = this.Name,
                SiteName= this.SiteName, 
                SiteUrl=this.SiteUrl,
                Method = this.Method,
                Encoding = this.Encoding,

                BooksUrl = this.BooksUrl,
                BookNamePrefix = this.BookNamePrefix,
                BookName = this.BookName,
                BookRegex = this.BookRegex,
                BookRegexTag = this.BookRegexTag,

                ChapterRegex = this.ChapterRegex,
                ChapterRegexTag = this.ChapterRegexTag,
                ChaptersUrl = this.ChaptersUrl,

                ContentBaseUrl = this.ContentBaseUrl,
                ContentRegex = this.ContentRegex,
                ContentClears = this.ContentClears 
            };

            return analizer;
        }

        public AnalizerBase() { }

        #region ISerializable
        protected AnalizerBase(SerializationInfo info, StreamingContext context)
        {
            this.Name = info.GetString("Name");
            this.SiteName = info.GetString("SiteName");
            this.SiteUrl = info.GetString("SiteUrl");
            this.Encoding = Encoding.GetEncoding(info.GetString("Encoding"));
            this.Method = (HttpMethod)info.GetValue("Method", typeof(HttpMethod));
            this.BookNamePrefix = info.GetString("BookNamePrefix");
            this.BookName = info.GetString("BookName");
            this.BooksUrl = info.GetString("BooksUrl");
            this.BookRegex = info.GetString("BookRegex");
            this.BookRegexTag = (string[])info.GetValue("BookRegexTag", typeof(string[]));
            this.ChaptersUrl = info.GetString("ChaptersUrl");
            this.ChapterRegex = info.GetString("ChapterRegex");
            this.ChapterRegexTag = (string[])info.GetValue("ChapterRegexTag", typeof(string[]));
            this.ContentBaseUrl = info.GetString("ContentBaseUrl");
            this.ContentRegex = info.GetString("ContentRegex");
            this.ContentClears = (Dictionary<string, string>)info.GetValue("ContentClears", typeof(Dictionary<string, string>));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Name", this.Name);
            info.AddValue("SiteName", this.SiteName);
            info.AddValue("SiteUrl", this.SiteUrl);
            info.AddValue("Encoding", this.Encoding.BodyName);
            info.AddValue("Method", this.Method);
            info.AddValue("BookNamePrefix", this.BookNamePrefix);
            info.AddValue("BookName", this.BookName);
            info.AddValue("BooksUrl", this.BooksUrl);
            info.AddValue("BookRegex", this.BookRegex);
            info.AddValue("BookRegexTag", this.BookRegexTag);
            info.AddValue("ChaptersUrl", this.ChaptersUrl);
            info.AddValue("ChapterRegex", this.ChapterRegex);
            info.AddValue("ChapterRegexTag", this.ChapterRegexTag);
            info.AddValue("ContentBaseUrl", this.ContentBaseUrl);
            info.AddValue("ContentRegex", this.ContentRegex);
            info.AddValue("ContentClears", this.ContentClears);
        } 
        #endregion
    }
}
